package retry

import (
	"context"
	"errors"
	"fmt"
	"time"
)

var (
	// ErrRetryLimited ertryable function must be set limit, Either, timeout or number of retries
	ErrRetryLimited = errors.New("no attempt limited")
)

// RetryFunc retryable function
type RetryFunc func() error

// RetryConfig config of retryable function
type RetryConfig struct {
	interval time.Duration // interval time for attempt
	timeout  time.Duration // timeout for attempt
	counter  int           // numbers for attempt
}

// RetryOptions options for retryable function
type RetryOptions func(rc *RetryConfig)

// WithInterval set interval time for fretryable function
func WithInterval(interval time.Duration) RetryOptions {
	return func(rc *RetryConfig) {
		rc.interval = interval
	}
}

// WithExpire set expire time for fretryable function
func WithExpire(timeout time.Duration) RetryOptions {
	return func(rc *RetryConfig) {
		rc.timeout = timeout
	}
}

// WithCounter set attempt num for fretryable function
func WithCounter(counter int) RetryOptions {
	return func(rc *RetryConfig) {
		rc.counter = counter
	}
}

// NewRetryConfig get retry config
func NewRetryConfig(options ...RetryOptions) *RetryConfig {
	rc := new(RetryConfig)

	for _, option := range options {
		option(rc)
	}

	return rc
}

// Do exec function
func (rc *RetryConfig) Do(rf RetryFunc) error {
	if rc.timeout == 0 && rc.counter == 0 {
		return ErrRetryLimited
	}

	ctx, cancel := context.WithTimeout(context.Background(), rc.timeout)
	defer cancel()

	execNum := 0
	var Err error
	for {
		if rc.counter != 0 && execNum >= rc.counter {
			return Err
		}

		select {
		case <-ctx.Done(): // timeout
			return ctx.Err()
		default:
		}

		if err := rc.attempt(rf); err != nil {
			// TODO 每次的错误都要log
			fmt.Println(err)
			Err = err
			execNum++
			time.Sleep(rc.interval)

			continue
		}
	}
}

// attempt Executing entity
func (rc *RetryConfig) attempt(rf RetryFunc) error {
	return rf()
}
